﻿using Admin.Infra;
using Admin.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using MongoDB.Bson;
using MongoDB.Driver.Builders;
using MongoDB.Driver.GridFS;
using MongoDB.Driver.Linq;
using MongoDB.Driver;
using System.IO;
using HtmlAgilityPack;
using System.Text;
using Ionic.Zip;

namespace Admin.Controllers
{
    public class VersionController : Controller
    {
        public readonly VersionDBContext versionContext = new VersionDBContext();
        public readonly CompanyDBContext companyContext = new CompanyDBContext();


        private readonly string PluginTemplateLocation = Path.Combine(
                           AppDomain.CurrentDomain.BaseDirectory, @"Content\PluginTemplate\");

        private readonly string SolutionTempLocation = Path.Combine(
                   AppDomain.CurrentDomain.BaseDirectory, @"Content\temp\{0}\{1}\Plugin");

        private readonly string TemplateLocation = Path.Combine(
                   AppDomain.CurrentDomain.BaseDirectory, @"Content\temp\{0}\{1}\Template");

        private readonly string TemplateExtractLocation = Path.Combine(
                   AppDomain.CurrentDomain.BaseDirectory, @"Content\temp\{0}\{1}\Pages");

        private readonly string TemplateAssetLocation = Path.Combine(
                  AppDomain.CurrentDomain.BaseDirectory, @"Content\temp\{0}\{1}\Assets");

        private readonly string ActionMethodFormat = @"public ActionResult {0}(Dictionary<BusinessLayer.Enums.DataBagKey, IMultipleResults> data = null) 
                                                    {{
                                                        return View();
                                                    }}
                                                    ";
        public int CompanyID = 0;
        public int VersionID = 0;

        Admin.Models.Version currentVersion = new Models.Version();


        public ActionResult Index(int id = 0)
        {
            var viewModel = new ViewModel();
            IQueryable<Admin.Models.Company> companies = companyContext.Companies.AsQueryable();
            IQueryable<Admin.Models.Version> versions = versionContext.Versions.AsQueryable();
            Session["cid"] = id.ToString();
            var CQuery = Query<Admin.Models.Company>.EQ(p => p.CID, id);
            var versionIDs = companies.Where(p => CQuery.Inject()).Select(q => q.Versions).FirstOrDefault();
            viewModel.Company = new Company { CID = id };
            var VQuery = Query<Admin.Models.Version>.In(p => p.VersionID, versionIDs);
            viewModel.Versions = versions.Where(r => VQuery.Inject()).ToList();
            viewModel.PageType = Enums.AdminPages.VersionList;

            var json = new Admin.Models.Version();
            var jsonSerializer = new System.Web.Script.Serialization.JavaScriptSerializer();
            viewModel.VersionJson = jsonSerializer.Serialize(json);

            return View(viewModel);
        }

        [HttpPost]
        public JsonResult Create(Admin.Models.Version version)
        {
            version.CreatedDate = DateTime.Now;
            version.ModifiedDate = DateTime.Now;
            int cid = Convert.ToInt32(Session["cid"]);
            Upsert(version, cid);
            return Json("success", JsonRequestBehavior.AllowGet);
        }

        public ActionResult Detail(int vid = 0, int cid = 0)
        {
            VersionID = vid;
            CompanyID = cid;
            Session["cid"] = cid;
            Session["vid"] = vid;
            var viewModel = new ViewModel();
            var VQuery = Query<Admin.Models.Version>.EQ(p => p.VersionID, vid);
            IQueryable<Admin.Models.Version> versions = versionContext.Versions.AsQueryable();
            viewModel.FileCollection = new List<FileInfo>();

            viewModel.Version = versions.Where(r => VQuery.Inject()).FirstOrDefault();
            viewModel.PageType = Enums.AdminPages.VersionDetail;
            string root = string.Format(TemplateExtractLocation, cid, vid);
            List<FileInfo> FileCollection = new List<FileInfo>();
            FileCollection = TraverseTree(root);
            viewModel.FileCollection = FileCollection;
            return View(viewModel);
        }

        [HttpPost]
        public ActionResult Upload(int id)
        {
            var viewModel = new ViewModel();
            string root = string.Format(TemplateExtractLocation, Session["cid"].ToString(), Session["vid"].ToString());

            foreach (string file in Request.Files)
            {
                HttpPostedFileBase hpf = Request.Files[file] as HttpPostedFileBase;
                if (hpf.ContentLength == 0)
                    continue;
                string savedFileName = Path.Combine(
                   string.Format(TemplateLocation, Session["cid"].ToString(), Session["vid"].ToString()),
                   Path.GetFileName(hpf.FileName));

                bool isExists = System.IO.Directory.Exists(Path.Combine(
                   string.Format(TemplateLocation, Session["cid"].ToString(), Session["vid"].ToString())));

                if (!isExists)
                    System.IO.Directory.CreateDirectory(Path.Combine(
                   string.Format(TemplateLocation, Session["cid"].ToString(), Session["vid"].ToString())));


                hpf.SaveAs(savedFileName);
                ExtractMockups(savedFileName);
                List<FileInfo> FileCollection = new List<FileInfo>();
                FileCollection = TraverseTree(root);
                viewModel.FileCollection = FileCollection;
            }

            var VQuery = Query<Admin.Models.Version>.EQ(p => p.VersionID, id);
            IQueryable<Admin.Models.Version> versions = versionContext.Versions.AsQueryable();

            viewModel.Version = versions.Where(r => VQuery.Inject()).FirstOrDefault();
            viewModel.PageType = Enums.AdminPages.VersionDetail;
            DirectoryCopy(root, string.Format(TemplateAssetLocation, Session["cid"].ToString(), Session["vid"].ToString()), true, true);
            return RedirectToAction("detail", new { cid = Session["cid"].ToString(), vid = Session["vid"].ToString() });
        }

        public ActionResult ProcessPages()
        {
            var viewModel = new ViewModel();
            var pageList = Request.Cookies["ProcessPages"];

            var VQuery = Query<Admin.Models.Version>.EQ(p => p.VersionID, Convert.ToInt32(Session["vid"]));
            IQueryable<Admin.Models.Version> versions = versionContext.Versions.AsQueryable();

            currentVersion = versions.Where(r => VQuery.Inject()).FirstOrDefault();

            List<string> pages = new List<string>();
            if (pageList != null)
                pages = HttpUtility.UrlDecode(pageList.Value).Split(',').ToList<string>();

            GeneratePlugins(pages);
            Upsert(currentVersion, Convert.ToInt32(Session["cid"]));
            return View();
        }

        public ActionResult Version()
        {
            var viewModel = new ViewModel();
            viewModel.Versions = versionContext.Versions.FindAll().ToList();
            var json = new Admin.Models.Version();
            json.Pages = new List<Page>();
            json.GlobalPlugins = new List<Models.Plugin>();
            //json.FileIncludes = new List<Include>();
            var jsonSerializer = new System.Web.Script.Serialization.JavaScriptSerializer();
            viewModel.PluginJson = jsonSerializer.Serialize(json);
            return View(viewModel);
        }

        private void Upsert(Admin.Models.Version version, int CID)
        {
            if (string.IsNullOrEmpty(version._id))
                version._id = ObjectId.GenerateNewId().ToString();
            versionContext.Versions.Update(Query.EQ("VersionName", version.VersionName),
                                         Update.Replace(version),
                                         UpdateFlags.Upsert);

            companyContext.Companies.Update(Query.EQ("CID", CID),
                    Update.PushWrapped("Versions", version.VersionID));
        }

        private void GeneratePlugins(List<string> pages)
        {
            if (currentVersion.Pages == null)
                currentVersion.Pages = new List<Page>();

            foreach (var item in pages)
            {
                var plugins = currentVersion.Pages.SelectMany(x => x.Plugins).ToList();
                Page p = new Page();
                p.PageName = Path.GetFileNameWithoutExtension(item);
                p.PageID = currentVersion.Pages.Count > 0 ? currentVersion.Pages.Max(x => { return x != null ? x.PageID : 0; }) + 1 : 1;
                p._id = ObjectId.GenerateNewId().ToString();
                p.CreatedDate = DateTime.Now;
                p.FileIncludes = null;//TODO
                p.IsActive = false;
                p.ModifiedDate = DateTime.Now;
                HtmlDocument htmlDocument = new HtmlDocument();
                htmlDocument.Load(item);
                List<Plugin> pl = new List<Plugin>();
                foreach (HtmlNode componentNode in htmlDocument.DocumentNode.SelectNodes("//*[@data-type='component']"))
                {
                    int pluginID = p.PageID;
                    string pluginName = componentNode.Attributes["data-name"].Value;
                    var modesCSV = componentNode.Attributes.Where(a => a.Name.Contains("data-mode")).Select(q => q.Value).FirstOrDefault();
                    if (!plugins.Any(x => x.PluginName == pluginName))
                    {
                        DirectoryCopy(PluginTemplateLocation, Path.Combine(string.Format(SolutionTempLocation, Session["cid"].ToString(), Session["vid"].ToString()), pluginName), true);
                        DirectoryRename(pluginName);
                        FileRename(pluginName);
                        SetControllerActions(pluginName, modesCSV, componentNode.OuterHtml);
                    }
                    componentNode.Attributes.Add("data-default", modesCSV.Split(',').FirstOrDefault());
                    List<Mode> mo = new List<Mode>();
                    foreach (var m in modesCSV.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
                    {
                        mo.Add(new Mode { _id = ObjectId.GenerateNewId().ToString(), ModeName = m, IsActive = false });
                    }
                    pl.Add(new Plugin
                    {
                        _id = ObjectId.GenerateNewId().ToString(),
                        CreatedDate = DateTime.Now,
                        IsActive = false,
                        ModifiedDate = DateTime.Now,
                        Mode = modesCSV.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries).FirstOrDefault(),
                        PluginID = pluginID + 1,
                        PluginName = pluginName,
                        SortOrder = 0,
                        Modes = mo
                    });
                }

                p.Plugins = new List<Plugin>();
                p.Plugins.AddRange(pl);
                htmlDocument.Save(item);
                p.VersionHTML = htmlDocument.DocumentNode.OuterHtml;
                var currentPage = currentVersion.Pages.Where(page => page.PageName == p.PageName).FirstOrDefault();
                if (currentPage != null)
                {
                    currentPage = p;
                }
                else
                {
                    currentVersion.Pages.Add(p);
                }
            }
        }

        private void DirectoryCopy(string sourceDirName, string destDirName, bool copySubDirs, bool copyAssetsOnly = false)
        {
            DirectoryInfo dir = new DirectoryInfo(sourceDirName);
            DirectoryInfo[] dirs = dir.GetDirectories();

            // If the source directory does not exist, throw an exception.
            if (!dir.Exists)
            {
                throw new DirectoryNotFoundException(
                    "Source directory does not exist or could not be found: "
                    + sourceDirName);
            }

            // If the destination directory does not exist, create it.
            if (!Directory.Exists(destDirName))
            {
                Directory.CreateDirectory(destDirName);
            }


            // Get the file contents of the directory to copy.
            FileInfo[] files = dir.GetFiles();

            foreach (FileInfo file in files)
            {
                // Create the path to the new copy of the file.
                string temppath = Path.Combine(destDirName, file.Name);

                if (copyAssetsOnly)
                {
                    if (Path.GetExtension(file.FullName) != ".html")
                        file.CopyTo(temppath, true);
                }
                else
                    file.CopyTo(temppath, true);
            }

            // If copySubDirs is true, copy the subdirectories.
            if (copySubDirs)
            {
                foreach (DirectoryInfo subdir in dirs)
                {
                    // Create the subdirectory.
                    string temppath = Path.Combine(destDirName, subdir.Name);

                    // Copy the subdirectories.
                    DirectoryCopy(subdir.FullName, temppath, copySubDirs);
                }
            }
        }

        //rename directory name with token
        private void DirectoryRename(string plugin)
        {
            IEnumerable<string> list = Directory.GetDirectories(Path.Combine(string.Format(SolutionTempLocation, Session["cid"].ToString(), Session["vid"].ToString()), plugin), "$plugin$", SearchOption.AllDirectories);
            foreach (var item in list)
            {
                DirectoryInfo dir = new DirectoryInfo(item);
                dir.MoveTo(item.Replace("$plugin$", plugin));
            }
        }

        //rename files and replace tokens inside filess
        private void FileRename(string plugin)
        {
            string[] filePaths = Directory.GetFiles(Path.Combine(string.Format(SolutionTempLocation, Session["cid"].ToString(), Session["vid"].ToString()), plugin), "*.*", SearchOption.AllDirectories);
            string currentFile = string.Empty;

            //replace token in files
            foreach (string file in filePaths)
            {
                currentFile = System.IO.File.ReadAllText(file);
                if (currentFile.Contains("$plugin$"))
                {
                    currentFile = currentFile.Replace("$plugin$", plugin);
                    using (StreamWriter streamWriter = new StreamWriter(file))
                    {
                        streamWriter.Write(currentFile);
                    }
                }
            }
            foreach (string file in filePaths)
            {
                string currentFileName = Path.GetFullPath(file);
                currentFileName = currentFileName.Replace("$plugin$", plugin);
                System.IO.File.Move(file, currentFileName);
            }
        }

        private void SetControllerActions(string plugin, string modesCSV, string html)
        {
            IEnumerable<string> list = Directory.GetFiles(Path.Combine(string.Format(SolutionTempLocation, Session["cid"].ToString(), Session["vid"].ToString()), plugin), string.Concat("*.cs"), SearchOption.AllDirectories);
            string currentFile = string.Empty;

            //replace token in files
            foreach (string file in list)
            {
                currentFile = System.IO.File.ReadAllText(file);
                if (currentFile.Contains("$modes$"))
                {
                    StringBuilder sb = new StringBuilder();
                    var modes = modesCSV.Split(',');
                    foreach (var mode in modes)
                    {
                        sb.Append(string.Format(ActionMethodFormat, mode));
                    }
                    currentFile = currentFile.Replace("$modes$", sb.ToString());

                    using (StreamWriter streamWriter = new StreamWriter(file))
                    {
                        streamWriter.Write(currentFile);
                    }
                }
            }
            string viewPath = Directory.GetFiles(Path.Combine(string.Format(SolutionTempLocation, Session["cid"].ToString(), Session["vid"].ToString()), plugin), "*.cshtml", SearchOption.AllDirectories).FirstOrDefault();
            using (StreamWriter streamWriter = new StreamWriter(viewPath))
            {
                streamWriter.Write(html);
            }

            foreach (var item in modesCSV.Split(','))
            {
                if (!Path.GetFileNameWithoutExtension(viewPath).Equals(item, StringComparison.InvariantCultureIgnoreCase))
                    System.IO.File.Copy(viewPath, Path.Combine(Path.GetDirectoryName(viewPath), item + ".cshtml"), true);
            }

        }

        private void ExtractMockups(string template)
        {
            string unpackDirectory = string.Format(TemplateExtractLocation, Session["cid"].ToString(), Session["vid"].ToString());
            using (ZipFile zip1 = ZipFile.Read(template))
            {
                // here, we extract every entry, but we could extract conditionally
                // based on entry name, size, date, checkbox status, etc.  
                foreach (ZipEntry e in zip1)
                {
                    e.Extract(unpackDirectory, ExtractExistingFileAction.OverwriteSilently);
                }
            }
        }

        //http://msdn.microsoft.com/en-us/library/bb513869.aspx
        private List<FileInfo> TraverseTree(string root, bool Parse = false)
        {
            Stack<string> dirs = new Stack<string>(1000);
            List<FileInfo> FileCollection = new List<FileInfo>();
            if (!System.IO.Directory.Exists(root))
            {
                return new List<FileInfo>();
            }
            dirs.Push(root);
            while (dirs.Count > 0)
            {
                string currentDir = dirs.Pop();
                string[] subDirs;
                try
                {
                    subDirs = System.IO.Directory.GetDirectories(currentDir);
                }
                catch (UnauthorizedAccessException e)
                {
                    //Console.WriteLine(e.Message);
                    continue;
                }
                catch (System.IO.DirectoryNotFoundException e)
                {
                    //Console.WriteLine(e.Message);
                    continue;
                }

                string[] files = null;
                try
                {
                    files = System.IO.Directory.GetFiles(currentDir);
                }

                catch (UnauthorizedAccessException e)
                {

                    // Console.WriteLine(e.Message);
                    continue;
                }

                catch (System.IO.DirectoryNotFoundException e)
                {
                    //Console.WriteLine(e.Message);
                    continue;
                }
                foreach (string file in files)
                {
                    try
                    {
                        // Perform whatever action is required in your scenario.
                        System.IO.FileInfo fi = new System.IO.FileInfo(file);
                        if (fi.Extension == ".html")
                            FileCollection.Add(fi);
                    }
                    catch (System.IO.FileNotFoundException e)
                    {
                        // If file was deleted by a separate application 
                        //  or thread since the call to TraverseTree() 
                        // then just continue.
                        // Console.WriteLine(e.Message);
                        continue;
                    }
                }
                foreach (string str in subDirs)
                    dirs.Push(str);
            }

            return FileCollection;
        }
    }
}
